var utils=require("../util.js")
var bigInt=require("./BigInteger.min.js")
var CryptoJS=require("./cryptojs-aes.min.js")
var asmCrypto=require("./asmcrypto.all.js")
require("./all.min.js")

var CRYPTO = function () {
  this.init();
};



CRYPTO.prototype.getAesCmac = function (hex_key, hex_message) {
  let key = CryptoJS.enc.Hex.parse(hex_key);
  let message = utils.byteArrayToWordArray(utils.hexToBytes(hex_message));
  return CryptoJS.CMAC(key, message);
}

CRYPTO.prototype.s1 = function (M) {
  let cmac = this.getAesCmac(this.ZERO, M);
  return cmac.toString();
}

CRYPTO.prototype.k1 = function (M, SALT, P) {
  let T = this.getAesCmac(SALT.toString(), M)
  return this.getAesCmac(T.toString(), P);
}


CRYPTO.prototype.k2 = function (N, P) {
  // T = AES-CMACsalt (N)
  let T = this.getAesCmac(this.k2_salt.toString(), N);
  // T0 = empty string (zero length)
  let T0 = "";
  // T1 = AES-CMACt (T0 || P || 0x01)
  let M1 = T0 + P.toString() + "01";
  let T1 = this.getAesCmac(T.toString(), M1);
  // T2 = AES-CMACt (T1 || P || 0x02)
  let M2 = T1 + P.toString() + "02";
  let T2 = this.getAesCmac(T.toString(), M2);
  // T3 = AES-CMACt (T2 || P || 0x03)
  let M3 = T2 + P.toString() + "03";
  let T3 = this.getAesCmac(T.toString(), M3);
  // k2(N, P) = (T1 || T2 || T3) mod 2(263)
  let T123 = T1 + T2 + T3;
  let TWO_POW_263 = bigInt(2).pow(263);
  let T123_bigint = bigInt(T123, 16);
  let modval = T123_bigint.divmod(TWO_POW_263);
  let modval_bigint = bigInt(modval.remainder);
  let k2_hex = utils.bigIntegerToHexString(modval_bigint);
  let k2_material = {
    NID: 0,
    encryption_key: 0,
    privacy_key: 0
  };
  k2_material.NID = k2_hex.substring(0, 2);
  k2_material.encryption_key = k2_hex.substring(2, 34);
  k2_material.privacy_key = k2_hex.substring(34, 66);
  return k2_material;
};

CRYPTO.prototype.k3 = function (N) {
  // T = AES-CMACsalt (N)
  let T = this.getAesCmac(this.k3_salt.toString(), N);
  // k3(N) = AES-CMACt ( “id64” || 0x01 ) mod 2^64
  let k3_cmac = this.getAesCmac(T.toString(), this.id64_hex + "01");
  let k3_cmac_bigint = bigInt(k3_cmac.toString(), 16);
  let TWO_POW_64 = bigInt(2).pow(64);
  let k3_modval = k3_cmac_bigint.divmod(TWO_POW_64);
  let k3_modval_bigint = bigInt(k3_modval.remainder);
  let k3_material = utils.bigIntegerToHexString(k3_modval_bigint);
  return k3_material;
};

CRYPTO.prototype.k4 = function (N) {
  // K4(N) = AES-CMACt ( “id6” || 0x01 ) mod 2^6
  let T = this.getAesCmac(this.k4_salt.toString(), N);
  let k4_cmac = this.getAesCmac(T.toString(), this.id6_hex + "01");
  let k4_cmac_bigint = bigInt(k4_cmac.toString(), 16);
  let k4_modval = k4_cmac_bigint.divmod(64);
  let k4_modval_bigint = bigInt(k4_modval.remainder);
  let k4_material = utils.bigIntegerToHexString(k4_modval_bigint);
  return k4_material;
};

CRYPTO.prototype.encryptedProvisioningData = function (sessionKey, sessionNonce, provisioningData) {
  let u8_sessionKey = utils.hexToU8A(sessionKey);
  let u8_sessionNonce = utils.hexToU8A(sessionNonce);
  let u8_provisioningData= utils.hexToU8A(provisioningData);
  var result = {
    encryptedProvisioningData: 0,
    provisioningDataMIC: 0
  };
  let encryptedProvisioningData = asmCrypto.AES_CCM.encrypt(u8_provisioningData, u8_sessionKey, u8_sessionNonce, new Uint8Array([]), 8);
  let hex = utils.u8AToHexString(encryptedProvisioningData);
  result.encryptedProvisioningData = hex.substring(0, hex.length - 16);
  result.provisioningDataMIC = hex.substring(hex.length - 16, hex.length);
  return result;
};

CRYPTO.prototype.meshAuthEncAccessPayload = function (hex_appkey, hex_nonce, hex_payload,ctl) {
  let u8_key = utils.hexToU8A(hex_appkey);
  let u8_nonce = utils.hexToU8A(hex_nonce);
  let u8_payload = utils.hexToU8A(hex_payload);
  let tagSize = ctl == null || ctl == 0 ? 4 : 8;
  var result = {
    EncAccessPayload: 0,
    TransMIC: 0
  };
  let auth_enc_access = asmCrypto.AES_CCM.encrypt(u8_payload, u8_key, u8_nonce, new Uint8Array([]), tagSize);
  let hex = utils.u8AToHexString(auth_enc_access);
  result.EncAccessPayload = hex.substring(0, hex.length - tagSize*2);
  result.TransMIC = hex.substring(hex.length - tagSize * 2, hex.length);
  return result;
};

CRYPTO.prototype.meshAuthEncNetwork = function (hex_encryption_key, hex_nonce, hex_dst, hex_transport_pdu,ctl) {
  let arg3 = hex_dst + hex_transport_pdu;
  let tagSize=ctl==null||ctl==0?4:8;
  let result = {
    Encryption_Key: hex_encryption_key,
    EncDST: 0,
    EncTransportPDU: 0,
    NetMIC: 0
  };
  let u8_key = utils.hexToU8A(hex_encryption_key);
  let u8_nonce = utils.hexToU8A(hex_nonce);
  let u8_dst_plus_transport_pdu = utils.hexToU8A(arg3);
  let auth_enc_network = asmCrypto.AES_CCM.encrypt(u8_dst_plus_transport_pdu, u8_key, u8_nonce, new Uint8Array([]), tagSize);
  let hex = utils.u8AToHexString(auth_enc_network);
  result.EncDST = hex.substring(0, 4);
  result.EncTransportPDU = hex.substring(4, hex.length - tagSize*2);
  result.NetMIC = hex.substring(hex.length - tagSize * 2, hex.length);
  return result;
}

CRYPTO.prototype.decryptAndVerify = function (hex_key, hex_cipher, hex_nonce,ctl) {
  let dec_ver_result = {
    hex_decrypted: "",
    status: 0,
    error: ""
  }
  let tagSize = ctl == null || ctl == 0 ? 4 : 8;
  try {
    var adata = new Uint8Array([]);
    let dec = asmCrypto.AES_CCM.decrypt(utils.hexToU8A(hex_cipher), utils.hexToU8A(hex_key), utils.hexToU8A(hex_nonce), adata, tagSize);
    let hex_dec = utils.u8AToHexString(dec);
    dec_ver_result.hex_decrypted = hex_dec;
  } catch (err) {
    dec_ver_result.hex_decrypted = "";
    dec_ver_result.status = -1;
    dec_ver_result.error = err;
  }
  return dec_ver_result;
}
CRYPTO.prototype.e = function (hex_plaintext, hex_key) {
  var hex_padding = "";
  var ecb_encrypted = asmCrypto.AES_ECB.encrypt(asmCrypto.hex_to_bytes(hex_plaintext), asmCrypto.hex_to_bytes(hex_key),asmCrypto.hex_to_bytes(hex_padding));
  return asmCrypto.bytes_to_hex(ecb_encrypted);
};

CRYPTO.prototype.privacyRandom = function (enc_dst, enc_transport_pdu, netmic) {
  var temp = enc_dst + enc_transport_pdu + netmic;
  if (temp.length >= 14) {
    return temp.substring(0, 14);
  } else {
    return "";
  }
}

CRYPTO.prototype.obfuscate = function (enc_dst, enc_transport_pdu, netmic, ctl_ttl_hex, seq, src, iv_index, privacy_key) {
  //1. Create Privacy Random
  let hex_privacy_random = this.privacyRandom(enc_dst, enc_transport_pdu, netmic);
  var result = {
    privacy_key: '',
    privacy_random: '',
    pecb_input: '',
    pecb: '',
    ctl_ttl_seq_src: '',
    obfuscated_ctl_ttl_seq_src: ''
  };
  result.privacy_key = privacy_key;
  result.privacy_random = hex_privacy_random;
  //2. Calculate PECB
  result.pecb_input = "0000000000" + iv_index + hex_privacy_random;
  
  let pecb_hex = this.e(result.pecb_input, privacy_key);
  let pecb = pecb_hex.substring(0, 12);
  result.pecb = pecb;
  let ctl_ttl_seq_src = ctl_ttl_hex + seq + src;
  result.ctl_ttl_seq_src = ctl_ttl_seq_src;
  // 3. Obfuscate
  let obf = utils.xorU8Array(utils.hexToU8A(ctl_ttl_seq_src), utils.hexToU8A(pecb));
  result.obfuscated_ctl_ttl_seq_src = utils.u8AToHexString(obf);
  return result;
}

CRYPTO.prototype.deobfuscate = function (obfuscated_data, iv_index, netkey, privacy_random, privacy_key) {
  var result = {
    privacy_key: '',
    privacy_random: '',
    pecb_input: '',
    pecb: '',
    ctl_ttl_seq_src: '',
  };

  result.privacy_key = privacy_key;
  result.privacy_random = privacy_random;

  // derive PECB
  result.pecb_input = "0000000000" + iv_index + privacy_random;
  let pecb_hex = this.e(result.pecb_input, privacy_key);
  let pecb = pecb_hex.substring(0, 12);
  result.pecb = pecb;

  // DeobfuscatedData = ObfuscatedData ⊕ PECB[0–5]

  let deobf = utils.xorU8Array(utils.hexToU8A(obfuscated_data), utils.hexToU8A(pecb));
  result.ctl_ttl_seq_src = utils.u8AToHexString(deobf);
  return result;
}

CRYPTO.prototype.generateBeaconKey=function(netkey){
  return this.k1(netkey, this.k1_salt, this.id128_hex+"01").toString();
}

CRYPTO.prototype.init = function () {
  this.ZERO = '00000000000000000000000000000000';
  this.k1_salt = this.s1("6e6b626b"); // "nkbk";
  this.k2_salt = this.s1("736d6b32"); // "smk2"
  this.k3_salt = this.s1("736d6b33"); // "smk3";
  this.k4_salt = this.s1("736d6b34"); // "smk4";
  this.id128_hex = utils.bytesToHex(utils.toAsciiCodes('id128'));
  this.id64_hex = utils.bytesToHex(utils.toAsciiCodes('id64'));
  this.id6_hex = utils.bytesToHex(utils.toAsciiCodes('id6'));
}

module.exports = CRYPTO;
  